package dijkstra;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;
import be.kuleuven.cs.som.annotate.Value;
import roborally.supporting.PhysicalAmount;
import roborally.supporting.Orientation;
import roborally.supporting.Position;

/**
 * A class representing a vertex in a directed graph used by the Dijkstra algorithm.
 * The vertex is uniquely specified by its position. The orientation is needed for
 * restoring the path. The energy is needed for comparing one vertex with another.
 * 
 * @invar The position must not be null
 * 		| isValidPosition(this.getPosition())
 * @invar The orientation must not be null
 * 		| isValidOrientation(this.getOrientation())
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 2.0
 */
@Value
public class Vertex implements Comparable<Vertex> {
	
	/**
	 * Creates a new vertex with given parameters.
	 * 
	 * @param position The position to be set.
	 * @param orientation The orientation to be set.
	 * @param energyToReach The energyToReach to be set.
	 * @pre The position may not be a null object.
	 * 		| position != null
	 * @post
	 * 		| (new this).getPosition == position
	 * @post
	 * 		| (new this).getOrientation() == orientation
	 * @effect
	 * 		| setEnergyToReach(energyToReach)
	 */
	@Raw
	public Vertex(Position position,@Raw Orientation orientation, PhysicalAmount energyToReach) {
		this.position = position;
		this.orientation = orientation;  // null is allowed, so @Raw
		setEnergyToReach(energyToReach);
	}
	
	/**
	 * Returns whether this position is valid.
	 * 
	 * @param position The position to be controlled.
	 * @return
	 * 		| result == position != null
	 */
	public static boolean isValidPosition(Position position) {
		return position != null;
	}
	
	/**
	 * Returns the position of this.
	 */
	@Basic @Raw @Immutable
	public Position getPosition() {
		return this.position;
	}
	
	private final Position position;
	
	/**
	 * Returns the orientation of this.
	 */
	@Raw @Basic 
	public Orientation getOrientation() {
		return this.orientation; //can be null, so @Raw
	}
	
	/**
	 * Controls if this orientation is valid for a vertex.
	 * 
	 * @param orientation The orientation to be controlled.
	 * @return
	 * 		| result == orientation != null
	 */
	@Raw
	public static boolean isValidOrientation(Orientation orientation) {
		return orientation != null;
	}
	
	/**
	 * Sets the orientation of this.
	 * 
	 * @param orientation The orientation to be set.
	 * @pre
	 * 		| isValidOrientation(orientation)
	 * @post
	 * 		| (this new).getOrientation() == orientation
	 */
	@Raw
	public void setOrientation(Orientation orientation) {
		assert isValidOrientation(orientation);
		this.orientation = orientation;
	}
	
	private Orientation orientation;
	
	/**
	 * Returns the energyToReach of this.
	 */
	@Basic @Raw
	public PhysicalAmount getEnergyToReach() {
		return this.energyToReach;
	}
	
	/**
	 * Sets the energyToReach of this.
	 * 
	 * @param energyToReach The energyToReach of this.
	 * @post
	 * 		| (this new).getEnergyToReach() == energyToReach
	 */
	@Raw
	public void setEnergyToReach(PhysicalAmount energyToReach) {
		this.energyToReach = energyToReach;
	}
	
	private PhysicalAmount energyToReach;
	
	/**
	 * Compares this with vertex according to the comparable interface of java.
	 * 
	 * @param vertex The vertex to compare.
	 * @effect Returns the same values as applying compareTo on the energyToReach of this and the other vertex.
	 * 		| this.getEnergyToReach().compareTo(vertex.getEnergyToReach())
	 * @throws IllegalArgumentException
	 * 		| vertex == null
	 */
	@Override
	public int compareTo(Vertex vertex) throws IllegalArgumentException{
		if(vertex == null)
			throw new IllegalArgumentException("Cannot compare to null");
		return this.getEnergyToReach().compareTo(vertex.getEnergyToReach());
	}
	
	/**
	 * Checks whether this and object are the equal.
	 * 
	 * @param object The object to compare with.
	 * @result
	 * 		| if !Vertex.class.isInstance(object)
	 * 		| 	then result == false
	 * 		| else
	 * 		|	result == this.getPosition().equals(Vertex.class.cast(object).getPosition())
	 */
	@Override
	public boolean equals(Object object) {
		if(!Vertex.class.isInstance(object))
			return false;
		else
			return this.getPosition().equals(Vertex.class.cast(object).getPosition());
	}
	
	/**
	 * Return a hashCode of this object according the api specifications.
	 * @return
	 * 		| result == this.getPosition().hashCode()
	 */
	@Override
	public int hashCode() {
		return this.getPosition().hashCode();
	}
	
	/**
	 * Returns a string representation of this object.
	 * 
	 * @return
	 * 		| result == "Vertex: " + getPosition().toString() + ", " + getOrientation().getOrientation() + ", " + this.getEnergyToReach().toString()
	 */
	@Override
	public String toString() {
		return "Vertex: " + getPosition().toString() + ", " + getOrientation().toString() + ", " + this.getEnergyToReach().toString();  
	}
	
	/**
	 * Controls if the position of this vertex is next to the position of the given vertex.
	 * 
	 * @param vertex The vertex to be controlled.
	 * @effect
	 * 		| this.getPosition().nextTo(vertex.getPosition())
	 */
	public boolean nextTo(Vertex vertex) {
		return this.getPosition().nextTo(vertex.getPosition());
	}

}
